%{

#include <string.h>
#include "debug.h"
#include "opcodes.h"
#include "compiler.h"
#include "scratchpad.h"
#include "grammar.tab.h"

static void count_lines();
static int badlex();
static int parse_dec();
static int parse_float();
static int parse_char();
static int parse_string();
static int parse_ident();

#define return_assign(opcode) { yylval.number = opcode; return L_ASSIGN; }
#define return_order(opcode) { yylval.number = opcode; return L_ORDER; }

%}

 /* shortcut for basic regex */
 /* digit */
D			[0-9]
 /* letter */
L			[a-zA-Z_]
 /* exp */
E			[Ee][+-]?{D}+
 /* whitespace (including newline) */
S			[ \t\v\f\r\n]

 /* exclusive condition for comment */
%x COMMENT

 /* prevent the need for linking with -lfl */
%option noyywrap

%%
<COMMENT>"*/"			{ BEGIN(INITIAL); }
<COMMENT>[^*\n]+		{ /* eat comment in chunks */ }
<COMMENT>"*"			{ /* eat the lone star */ }
<COMMENT>\n			{ current_line++; }
<COMMENT><<EOF>>		{ yyerror("Unterminated comment"); BEGIN(INITIAL); yyterminate(); }
"/*"				{ BEGIN(COMMENT); }
"//"[^\n]*			{ /* ignore comment */ }

{L}({L}|{D})*			{ return parse_ident(); }

{D}+				{ return parse_dec(); }

{D}+{E}				{ return parse_float(); }
{D}*"."{D}+({E})?		{ return parse_float(); }
{D}+"."{D}*({E})?		{ return parse_float(); }

\"(\\(.|\n)|[^\\"\n])*\"	{ return parse_string(); }

'(\\(.|\n)|[^\\'\n])+'		{ return parse_char(); }

"+"				{ return '+'; }
"->"				{ return L_ARROW; }
"-"				{ return '-'; }
"&&"				{ return L_LAND; }
"||"				{ return L_LOR; }
"<="				{ return_order(F_LE); }
"<"				{ return_order(F_LT); }
">="				{ return_order(F_GE); }
">"				{ return_order(F_GT); }
"*"				{ return '*'; }
"%"				{ return '%'; }
"/"				{ return '/'; }
"=="				{ return L_EQ; }
"="				{ return_assign(F_ASSIGN); }
"("{S}*"{"			{ count_lines(); return L_ARRAY_OPEN; }
"("{S}*"["			{ count_lines(); return L_MAPPING_OPEN; }
"("				{ return '('; }
")"				{ return ')'; }
"{"				{ return '{'; }
"}"				{ return '}'; }
"["				{ return '['; }
"]"				{ return ']'; }
";"				{ return ';'; }
","				{ return ','; }
"!="				{ return L_NE; }
"!"				{ return L_NOT; }
":"				{ return ':'; }

^#.*				{ /* TODO: lineno after preprocess */ }
\n				{ current_line++; }
[ \t\v\f\r]			{ /* ignore whitespace */ }
.				{ return badlex(); }

%%

static void count_lines()
{
	char *outp = yytext;
	while (*outp) {
		if (*outp == '\n') {
			current_line++;
		}
		outp++;
	}
}

static int badlex()
{
	char buff[100];

	sprintf(buff, "Illegal character (hex %02x) '%c'", (unsigned) yytext[0], (char) yytext[0]);

	yyerror(buff);

	return ' ';
}

static int parse_dec()
{
	yylval.number = (int) strtol(yytext, (char **) NULL, 10);
	return L_NUMBER;
}

static int parse_float()
{
	float myreal;
	sscanf(yytext, "%f", &myreal);
	yylval.real = (float) myreal;
	return L_REAL;
}

static int parse_char()
{
	char *outp = yytext + 1;
	if (*outp++ == '\\') {
		switch(*outp++) {
			case 'n': yylval.number = '\n'; break;
			case 't': yylval.number = '\t'; break;
			case 'r': yylval.number = '\r'; break;
			case 'b': yylval.number = '\b'; break;
			case '\'': yylval.number = '\''; break;
			case '"': yylval.number = '"'; break;
			case '\\': yylval.number = '\\'; break;
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7':
				   outp--;
				   yylval.number = strtol(outp, &outp, 8);
				   if (yylval.number > 63) {
					   yywarn("Illegal character constant (oct overflow).");
					   yylval.number = 'x';
				   }
				   break;
			case 'x':
				   yylval.number = strtol(outp, &outp, 16);
				   if (yylval.number > 255) {
					   yywarn("Illegal character constant (hex overflow).");
					   yylval.number = 'x';
				   }
				   break;
			default:
				   yywarn("Unknown \\ escape.");
				   yylval.number = *(outp - 1);
				   break;
		}
	} else {
		yylval.number = *(outp - 1);
	}

	if (*outp++ != '\'') {
		outp--;
		yyerror("Illegal character (bad ending).");
		yylval.number = 0;
	}

	return L_NUMBER;
}

static int parse_string()
{
	yylval.string = scratch_copy_string(yytext);
	return L_STRING;
}

static int parse_ident()
{
	ident_hash_elem_t *ihe;
	if ((ihe = lookup_ident(yytext))) {
		if (ihe->token & IHE_RESWORD) {
			yylval.number = ihe->sem_value;
			return ihe->token & TOKEN_MASK;
		}
		yylval.ihe = ihe;
		return L_DEFINED_NAME;
	}
	yylval.string = scratch_copy(yytext);
	return L_IDENTIFIER;
}

void start_new_file(int f)
{
	FILE *file = fdopen(f, "r");

	current_line = 1;
	yy_switch_to_buffer(yy_create_buffer(file, YY_BUF_SIZE));
}

void end_new_file()
{
	/* TODO: anything to clean? */
}
